package com.sskj.ffmpeg;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Like
 * @Date: 2024/12/20 14:15
 * @Description: 在Windows环境下启动ffmpeg进行RTSP流转码HLS
 */
@Component
@Slf4j
public class FfmpegRtspConvertHls {

    /**
     * 存储转码任务
     */
    private static final ConcurrentHashMap<String, FfmpegRtspConvertHls.CoverThread> coverMap = new ConcurrentHashMap<>();

    /**
     * ffmpeg 路径
     */
    @Value("${ffmpeg.ffmpegPath}")
    private String FFMPEG_PATH;

    /**
     * 文件输出根路径
     */
    @Value("${ffmpeg.rootOutputPath}")
    private String ROOT_OUTPUT_PATH;

    /**
     * 开启转换
     */
    public String startTransform(String code, String rtspUrl) {
        String hlsFileOutputPath = buildHlsFileOutputPath(code);
        String[] command = buildFfmpegCommand(rtspUrl, hlsFileOutputPath);

        String filePath = "/" + code + "/" + code + ".m3u8";
        String memKey = "startLive" + code;

        synchronized (memKey.intern()) {
            stopExistingTransformation(code);
            FfmpegRtspConvertHls.CoverThread thread = new FfmpegRtspConvertHls.CoverThread(code, rtspUrl, hlsFileOutputPath, command);
            coverMap.put(code, thread);
            thread.start();
        }
        return filePath;
    }

    /**
     * 停止转换
     */
    public void stopTransform(String code) {
        String memKey = "startLive" + code;
        synchronized (memKey.intern()) {
            stopExistingTransformation(code);
        }
    }

    private void stopExistingTransformation(String code) {
        if (coverMap.containsKey(code)) {
            FfmpegRtspConvertHls.CoverThread thread = coverMap.get(code);
            if (thread != null && thread.getTaskState() != FfmpegRtspConvertHls.CoverThread.FAIL) {
                thread.stopConvert();
                log.info("结束 {} 转换线程", code);
            }
        }
    }

    private String buildHlsFileOutputPath(String code) {
        return ROOT_OUTPUT_PATH + "\\" + code + "\\" + code + ".m3u8";
    }

    private String[] buildFfmpegCommand(String rtspUrl, String hlsFileOutputPath) {
        return new String[]{
                FFMPEG_PATH,
                "-i", rtspUrl,
                "-c:v", "libx264",
                "-preset", "fast",
                "-crf", "20",
                "-c:a", "aac",
                "-b:a", "128k",
                "-f", "hls",
                "-hls_time", "10",
                "-hls_list_size", "10",
                "-hls_flags", "delete_segments",
                hlsFileOutputPath
        };
    }

    /**
     * 执行命令线程
     */
    private class CoverThread extends Thread {

        // 摄像机编号
        private final String code;

        // RTSP 地址
        private final String rtspUrl;

        // HLS 文件生成地址
        private final String hlsFileOutputPath;

        // 输出文件名
        private final String outputDirPath;

        // 命令
        private final String[] command;

        // 运行状态 0未开始 1进行中 -1失败
        private volatile int taskState = 0;
        private static final int NOT_START = 0;
        private static final int RUNNING = 1;
        private static final int FAIL = -1;
        private Process process = null;

        CoverThread(String code, String rtspUrl, String hlsFileOutputPath, String[] command) {
            this.code = code;
            this.rtspUrl = rtspUrl;
            this.command = command;
            this.hlsFileOutputPath = hlsFileOutputPath;
            this.outputDirPath = ROOT_OUTPUT_PATH + "\\" + code + "\\";
            setName("m3u8-" + code);
            this.taskState = NOT_START;
        }

        @Override
        public void run() {
            try {
                checkAndCreateDirectory();
                // 创建 ProcessBuilder 实例
                ProcessBuilder processBuilder = new ProcessBuilder(command);
                processBuilder.redirectErrorStream(true);
                // 状态设置为运行中
                this.taskState = RUNNING;

                // 启动进程
                process = processBuilder.start();

                readProcessOutput();
            } catch (IOException e) {
                log.error("出现异常: {}", e.getMessage(), e);
                this.taskState = FAIL;
            }
        }

        private void checkAndCreateDirectory() {
            File dir = new File(outputDirPath);
            if (!dir.exists()) {
                boolean success = dir.mkdirs();
                log.info("创建目录 {} 是否成功：{}", outputDirPath, success);
            }
        }

        private void readProcessOutput() throws IOException {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                int lineCount = 0;
                while ((line = reader.readLine()) != null) {
                    log.info("line {}: {}", lineCount++, line);
                }
            }
        }

        /**
         * 获取任务执行状态
         */
        public int getTaskState() {
            return taskState;
        }

        /**
         * 停止转码
         */
        public void stopConvert() {
            if (process != null) {
                process.destroyForcibly();
                log.info("停止转码");
                process = null;
            }
            deleteFilesInDirectory(outputDirPath);
        }

        private void deleteFilesInDirectory(String directoryPath) {
            File directory = new File(directoryPath);

            if (directory.exists() && directory.isDirectory()) {
                File[] files = directory.listFiles();
                if (files != null) {
                    for (File file : files) {
                        if (file.isFile()) {
                            file.delete();
                            log.info("删除文件: {}", file.getName());
                        } else if (file.isDirectory()) {
                            log.info("跳过目录: {}", file.getName());
                        }
                    }
                    directory.delete();
                    log.info("删除目录: {}", directory.getName());
                } else {
                    log.warn("无法列出目录中的文件: {}", directoryPath);
                }
            } else {
                log.warn("指定的路径不是一个有效的目录: {}", directoryPath);
            }
        }
    }
}
